package jadeCW.behaviour;

import jade.core.behaviours.Behaviour;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jadeCW.PatientAgent;
import jadeCW.output.Printer;

/**
 * Propose Swap behaviour class
 * 
 * @author br08, dac108, dv08
 */
public class ProposeSwap extends Behaviour {
	/**
	 * Type identifier for message
	 */
	public static final String TYPE = "swap-appointment";

	/**
	 * Generated serialisation ID
	 */
	private static final long serialVersionUID = -1177057929888020031L;

	private int step = 0;
	private MessageTemplate template;

	@Override
	public void action() {
		// must be run on a patient
		if (!(myAgent instanceof PatientAgent)) {
			return;
		}

		PatientAgent patient = (PatientAgent) myAgent;

		// if the patient does not know a preferred owner, or already has its
		// preferred appointment, done
		if (!patient.knowsPreferredOwners()
				|| patient.getAppointment() == patient
						.getPreferredAppointment()) {
			return;
		}

		switch (this.step) {
		case 0:
			// create a request message
			ACLMessage request = new ACLMessage(ACLMessage.REQUEST);

			// send to the owner of the preferred appointment
			request.addReceiver(patient.getPreferredOwner());

			// create the content as the patient's current appointment and their
			// preferred appointment (other party needs to check our appointment
			// is not worse, and they still own theirs
			String content = patient.getAppointment() + "-"
					+ patient.getPreferredAppointment();

			// add the content
			request.setContent(content);

			// if the preferred owner is actually a hospital, send just the
			// preferred appointment
			for (DFAgentDescription hospital : patient
					.getAppointmentAllocators()) {
				if (hospital.getName().equals(patient.getPreferredOwner())) {
					request.setContent(String.valueOf(patient
							.getPreferredAppointment()));
					break;
				}
			}

			request.setConversationId(TYPE);
			request.setReplyWith(TYPE + System.currentTimeMillis());

			// prepare the template to get responses
			this.template = MessageTemplate.and(MessageTemplate
					.MatchConversationId(TYPE), MessageTemplate
					.MatchInReplyTo(request.getReplyWith()));

			// lock the patient to stop it accepting requests while waiting for
			// the response from the request
			patient.lockRequesting();

			Printer.printProposeSend(patient,
					patient.getPreferredAppointment(), patient
							.getPreferredOwner());

			// send the request
			patient.send(request);

			// next step
			this.step = 1;
			break;
		case 1:
			// receive the message
			ACLMessage reply = patient.receive(this.template);

			if (reply != null) {
				switch (reply.getPerformative()) {
				// if the swap has been accepted
				case ACLMessage.ACCEPT_PROPOSAL:

					Printer.printProposeReceiveAccept(patient, patient
							.getAppointment(), reply.getSender(), patient
							.getPreferredAppointment());

					// set the offered appointment
					patient.setAppointment(patient.getPreferredAppointment());

					boolean isHospital = false;
					ACLMessage inform = new ACLMessage(ACLMessage.INFORM);

					// send inform message to all hospitals which don't already
					// know (if we've swapped with a hospital they'll already
					// have changed our appointment)
					for (DFAgentDescription hospital : patient
							.getAppointmentAllocators()) {
						if (hospital.getName().equals(
								patient.getPreferredOwner())) {
							isHospital = true;
							break;
						}
						inform.addReceiver(hospital.getName());
					}

					if (isHospital) {
						break;
					}

					// create the inform message for the hospital
					inform.setContent(String.valueOf(patient
							.getPreferredAppointment()));
					inform.setConversationId(UpdateAppointments.TYPE);
					inform.setReplyWith(UpdateAppointments.TYPE
							+ System.currentTimeMillis());

					// send the inform and remove our preferred appointment
					patient.send(inform);
					patient.setPreferredAppointment(
							PatientAgent.NO_APPOINTMENT, null);
					patient.resetPreferences();

					break;
				case ACLMessage.REJECT_PROPOSAL:

					Printer.printProposeReceiveReject(patient, patient
							.getPreferredAppointment(), reply.getSender());

					// our swap has been rejected (either does not own it any
					// more, or it a worse offer) so find new owner
					// if the offer is worse, FindAppointmentOwner will find the
					// same owner again and stop
					patient.addBehaviour(new FindAppointmentOwner());

					break;
				case ACLMessage.REFUSE:

					Printer.printProposeReceiveRefuse(patient, patient
							.getPreferredAppointment(), reply.getSender());

					// we have been refused our request (probably the other
					// patient is in the middle of a swap, so try again soon)
					patient.unlockRequesting();

					this.step = 0;
					return;
				}

				// unlock the patient to allow it to accept requests
				patient.unlockRequesting();

				// finish the behaviour
				this.step = 2;
			} else {
				block();
			}

			break;
		}
	}

	@Override
	public boolean done() {
		return (this.step == 2);
	}
}
